package bupt.xixi.learning.beans.factory.xml;

import bupt.xixi.learning.beans.BeansException;
import bupt.xixi.learning.beans.PropertyValue;
import bupt.xixi.learning.beans.factory.config.BeanDefinition;
import bupt.xixi.learning.beans.factory.config.BeanReference;
import bupt.xixi.learning.beans.factory.support.AbstractBeanDefinitionReader;
import bupt.xixi.learning.beans.factory.support.BeanDefinitionRegistry;
import bupt.xixi.learning.core.io.Resource;
import bupt.xixi.learning.core.io.ResourceLoader;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.XmlUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import java.io.IOException;
import java.io.InputStream;

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
        super(registry);
    }

    public XmlBeanDefinitionReader(BeanDefinitionRegistry beanDefinitionRegistry,
                                   ResourceLoader resourceLoader) {
        super(beanDefinitionRegistry, resourceLoader);
    }

    @Override
    public void loadBeanDefinitions(Resource resource) throws BeansException {
        try(InputStream inputStream = resource.getInputStream()) {
            doLoadBeanDefinitions(inputStream);
        } catch (IOException | ClassNotFoundException e) {
            throw new BeansException("IOException parsing XML document from" + resource, e);
        }
    }


    @Override
    public void loadBeanDefinitions(Resource... resources) throws BeansException {
        for (Resource resource : resources) {
            loadBeanDefinitions(resource);
        }
    }

    @Override
    public void loadBeanDefinitions(String location) throws BeansException {
        ResourceLoader resourceLoader = getResourceLoader();
        Resource resource = resourceLoader.getResource(location);
        loadBeanDefinitions(resource);
    }

    @Override
    public void loadBeanDefinitions(String... locations) throws BeansException {
        for(String location: locations) {
            loadBeanDefinitions(location);
        }
    }


    protected void doLoadBeanDefinitions(InputStream inputStream) throws ClassNotFoundException {
        Document document = XmlUtil.readXML(inputStream);
        Element root = document.getDocumentElement();
        NodeList childNodes = root.getChildNodes();

        for (int i=0; i<childNodes.getLength(); i++) {
            // 1. 判断元素
            if (!(childNodes.item(i) instanceof Element)) continue;
            // 2. 判断对象
            if (!"bean".equals(childNodes.item(i).getNodeName())) continue;
            // 3. 解析标签
            Element bean = (Element) childNodes.item(i);
            String id = bean.getAttribute("id");
            String name = bean.getAttribute("name");
            String className = bean.getAttribute("class");
            String initMethod = bean.getAttribute("init-method");
            String destroyMethod = bean.getAttribute("destroy-method");
            String beanScope = bean.getAttribute("scope");

            // 4. 获取class，方便获取类中的名称
            Class<?> clazz = Class.forName(className);
            //    优先级 id>name
            String beanName = StrUtil.isNotEmpty(id)? id: name;
            if (StrUtil.isEmpty(beanName)) {
                beanName = StrUtil.lowerFirst(clazz.getSimpleName());
            }
            // 5. 定义bean, 通过解析bean下的子标签property
            BeanDefinition beanDefinition = new BeanDefinition(clazz);

            // 5.1 将init和destroy方法注册到beanDefinition里
            beanDefinition.setInitMethodName(initMethod);
            beanDefinition.setDestroyMethodName(destroyMethod);
            // 5.2 设置单例
            if (StrUtil.isNotEmpty(beanScope)) beanDefinition.setScope(beanScope);
            // 5.2 注册其他字段
            for (int j=0; j< bean.getChildNodes().getLength(); j++) {
                if (!(bean.getChildNodes().item(j) instanceof Element)) continue;
                if (!"property".equals(bean.getChildNodes().item(j).getNodeName())) continue;
                // 读出xml中的各个属性
                Element property = (Element) bean.getChildNodes().item(j);
                String attrName = property.getAttribute("name");
                String attrValue = property.getAttribute("value");
                String attrRefName = property.getAttribute("ref");
                // 如果是引用类型，用BeanReference包装一下，createBean的时候发现是BeanReference类型会帮忙转为bean
                Object value = StrUtil.isNotEmpty(attrRefName)? new BeanReference(attrRefName): attrValue;
                PropertyValue propertyValue = new PropertyValue(attrName, value);
                beanDefinition.getPropertyValues().addPropertyValue(propertyValue);
            }
            if (getRegistry().containsBeanDefinition(beanName))
                throw new BeansException("Duplicate beanName[" + beanName + "] is not allowed");
            getRegistry().registerBeanDefinition(beanName, beanDefinition);
        }

    }
}
